# -*- coding: utf-8 -*-
# copyright 2009  Nik Lutz
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#operationDescription
# You should have received a copy of the GNU General Public License
# along with this library; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
# USA
import sys, os, commands, time
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from PyKDE4.plasma import Plasma
from PyKDE4 import plasmascript
from PyKDE4.kdecore import *
from PyKDE4.kdeui import *
from PyKDE4.kio import *
from PyKDE4.solid import *

from ConfigParameters import *
from FilterParameters import *
from MiscParameters import *
from NagiosService import *
from NagiosUI import *
##
# A Nagios status monitor.
#
# I'd like to thank the following people (I copied a lot of their code):
# - Henri Wahl <h.wahl@ifw-dresden.de> author of Nagstamon (http://nagstamon.sf.net)
# - Mark McCans <mjmccans@gmail.com>  (author of gmail-plasmoid)
# - Jens Stegemann      (author of DaeMon Plasmoid) 
# 
# Build UI Files
# pyuic4 -o contents/code/ui_configparameters.py contents/ui/ConfigParameters.ui
# pyuic4 -o contents/code/ui_filterparameters.py contents/ui/FilterParameters.ui
# pyuic4 -o contents/code/ui_miscparameters.py contents/ui/MiscParameters.ui
##
class Nagiosoid(plasmascript.Applet):
    job = None
    nagios_server = None
    first_run = True
    filter_configuration = {}
    last_service_problems = {}
    check_timer = None
    NOTIFYRC="nagios-plasmoid.notifyrc"
    PLASMOID_VERSION=3
    configured_version=1
    misc_configuration={}
    poll_interval=1

    def __init__(self,parent,args=None):
        plasmascript.Applet.__init__(self,parent)


    def init(self):
        self.setHasConfigurationInterface(True)
        self.setAcceptDrops(False)
        self.InitSettings()
        self.initNotifications()
        self.initUI()

    def initUI(self):
        self.setAspectRatioMode(Plasma.Square)
        self.theme = Plasma.Svg(self)
        self.layout = QGraphicsLinearLayout(Qt.Vertical, self.applet)
        self.layout.setSizePolicy(QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding))
        self.icon = Plasma.IconWidget(KIcon(self.package().path() + "contents/icons/nagios-plasmoid-128.png"), "", self.applet)
        self.connect(self.icon, SIGNAL("clicked()"), self.openServiceView)

        self.setContentsMargins(0, 0, 0, 0)
        self.layout.setContentsMargins(0, 0, 0, 0)
        self.layout.addItem(self.icon)

    def resultData(self, job):
        if job.error() <> 0:
            self.debug("resultData: Error " + ob.errorText() )
            print "Error getting nagios ...", job.error(), job.errorText()
        else:
            #print ">", job
            self.notifyProblems()
        self.debug("resultData: check end")
        self.startTimer()

    def notifyProblems(self):
        if self.nagios_server.getFetchErrors() <> None :
            message = self.nagios_server.name + " <b>Error getting/parsing Nagios status</b>"
            for msg in self.nagios_server.getFetchErrors():
                message = message + "<br/>" +  msg
            self.fireNotification("new-alert", message)
            return None
        newProblems = self.getNewServcieProblems()
        diff = len(newProblems.keys())

        message = "Nagios detected <b>%s</b> new problems:" %diff
        if len(self.nagios_server.name ) > 0:
            message = "Nagios detected <b>%s</b> new problems on %s:" %(diff , self.nagios_server.name )
        
        for key in newProblems.keys():
            service = newProblems[key]
            message = message + "<br/>&nbsp;- " + service.host + " - " + service.name
        if len(newProblems) > 0 and self.first_run == False:
            self.fireNotification("new-alert", message)
        self.last_service_problems = {}
        for service in self.getServiceProblems():
            key = service.host+"-"+service.name
            self.last_service_problems[key] = service
        self.first_run = False


    def openServiceView(self):
        self.servicesDialog = ServiceView(self, self.serviceDialogSize )
        self.connect(self.servicesDialog, SIGNAL("dialogResized()"), self.dialogResized)
        self.servicesDialog.move(self.popupPosition(self.servicesDialog.sizeHint()))
        self.servicesDialog.show()
    
    def dialogResized(self):
        self.serviceDialogSize = self.servicesDialog.size()
        self.config().writeEntry("servicedialogsize", QVariant(self.serviceDialogSize))

############# Helper Methods

    def getNewServcieProblems(self):
        newProblems = {}
        self.debug( "getNewServcieProblems: Comparing ")
        self.debug( "getNewServcieProblems: NewServiceProblems:\n" + ("".join([str(x) for x in self.getServiceProblems()])))
        self.debug( "getNewServcieProblems: with ")
        self.debug( "getNewServcieProblems: LastServiceProblems:\n" +  ("".join([str(x) for x in self.last_service_problems.values()])))
        for service in self.getServiceProblems():
            key = service.host+"-"+service.name
            if key not in self.last_service_problems.keys():
                newProblems[key] = service
        self.debug( "getNewServcieProblems: Result (new problems):\n" + ("".join([str(x) for x in newProblems.values()])))
        return newProblems


    def getNumberServiceProblems(self):
        return len(self.getServiceProblems())

    def getServiceProblems(self):
        services = []
        for item in self.nagios_server.hosts:
            for servicename in self.nagios_server.hosts[item].services:
                 services.append(self.nagios_server.hosts[item].services[servicename])
        return services

    def getCriticalServices(self):
        services = []
        for service in self.getServiceProblems():
            if service.status == "CRITICAL":
                services.append(service)
        return services

    def getWarningServices(self):
        services = []
        for service in self.getServiceProblems():
            if service.status == "WARNING":
                services.append(service)
        return services


######################## Settings


    def InitSettings(self):
        # Setup configuration
        gc = self.config()
        cc =  FilterConfig()
        self.filter_configuration = self.getDefaultFilterConfiguration()
        for entry in self.filter_configuration:
            self.filter_configuration[entry] = str(gc.readEntry(entry, QVariant(self.filter_configuration[entry])).toString())
        cc.fromDict(self.filter_configuration)
        
        self.misc_configuration = self.getDefaultMiscSettings()
        for entry in self.misc_configuration:
            self.misc_configuration[entry] = str(gc.readEntry(entry, QVariant(self.misc_configuration[entry])).toString())

        cc.debug_mode = self.misc_configuration["debugoutput"]
        
        self.nagios_server = NagiosServer(conf=cc)
        self.nagios_server.name = str(gc.readEntry("servername", QVariant("servername")).toString())
        self.nagios_server.nagios_url = str(gc.readEntry("nagios_url", QVariant("http://www.example.com/nagios/")).toString())
        self.nagios_server.nagios_cgi_url = str(gc.readEntry("nagios_cgi_url",QVariant("http://www.example.com/cgi-bin/nagios3/")).toString())
        self.nagios_server.username = str(gc.readEntry("username", QVariant("username")).toString())
        self.nagios_server.password = "None"
        self.nagios_server.use_proxy_yes = bool(gc.readEntry("use_proxy_yes", QVariant(True)).toBool())
        self.nagios_server.use_proxy_no = bool(gc.readEntry("use_proxy_no", QVariant(False)).toBool())
        self.nagios_server.debug = bool(gc.readEntry("debug", QVariant(False)).toBool())
        self.configured_version = int(gc.readEntry("notifyrcversion", QVariant("1")).toInt()[0])
        self.poll_interval = int(gc.readEntry("pollinterval", QVariant("1")).toInt()[0])
        self.serviceDialogSize = gc.readEntry("servicedialogsize", QVariant(QSize (800,300))).toSize()
        # Get password from kwallet (async to avoid blocking)
        # NOTE: The wid and the value for async opening should be done better
        self.wallet = KWallet.Wallet.openWallet(KWallet.Wallet.LocalWallet(), 0, 1)
        if self.wallet <> None:
            self.connect(self.wallet, SIGNAL("walletOpened(bool)"), self.walletOpened)
        
    def walletOpened(self, status):
        if status:
            # Get passwords from wallet
            self.wallet.setFolder("nagios-plasmoid")
            passwd = QString()
            passwd = self.wallet.readPassword(self.nagios_server.username + "@" + self.nagios_server.name )[1]
            self.nagios_server.password = unicode(passwd)

            # Now start checking for emails
            if len(self.nagios_server.password) <> 0:
                self.checkNagios()
                self.startTimer()
            else:
                self.fireNotification("new-alert", self.nagios_server.name + ": no password for Nagios")

    def startTimer(self):
        self.stopTimer()
        self.check_timer = QTimer()
        self.connect(self.check_timer, SIGNAL("timeout()"), self.checkNagios)
        self.check_timer.start(self.poll_interval * 60000 ) 

    def stopTimer(self):
        if self.check_timer == None:
            pass
        else:
            self.check_timer.stop()

    def checkNow(self):
        if self.nagios_server.isChecking :
            self.debug("checkNow: nagios-servier is already checking")
            return False
        self.debug("checkNow: scheduling check")
        self.stopTimer()
        self.checkNagios()
        

    def checkNagios(self):
         # Test for network connection
        if Solid.Networking.status() == Solid.Networking.Connected or Solid.Networking.status() == Solid.Networking.Unknown:
            self.job = NagiosJob(self.nagios_server)
            self.connect(self.job, SIGNAL("result(KJob*)"), self.resultData)
            self.job.start()               
            self.debug("checkNagios: job started")
        self.update()

    def getPollInterval(self):
        return self.poll_interval
    #
    # ---------- Configuration ----------
    #
    def createConfigurationInterface(self, parent):
        # Remote Computer
        self.configParameters = ConfigParameters(self,  self.nagios_server)
        page = parent.addPage(self.configParameters, "Settings")
        page.setIcon(KIcon(self.package().path() + "contents/icons/nagios-plasmoid-128.png"))

        self.filterParameters = FilterParameters(self, self.filter_configuration )
        page = parent.addPage(self.filterParameters, "Filters")
        page.setIcon(KIcon("computer"))

        self.miscParameters = MiscParameters(self.misc_configuration)
        page = parent.addPage(self.miscParameters, "Interface")
        page.setIcon(KIcon("display"))

        self.connect(parent, SIGNAL("okClicked()"), self.configAccepted)
        self.connect(parent, SIGNAL("cancelClicked()"), self.configDenied)

    #
    # setup and show configuration dialog (called by plasma)
    #
    def showConfigurationInterface(self):
        if self.check_timer <> None:
            self.check_timer.stop()
        self.dialog = KPageDialog()
        self.dialog.setFaceType(KPageDialog.List)
        self.dialog.setButtons(KDialog.ButtonCode(KDialog.Ok | KDialog.Cancel))
        self.createConfigurationInterface(self.dialog)
        self.dialog.exec_()

    #
    # configuration done (ok-button)
    #
    def configAccepted(self):
        gc = self.config()
        self.miscParameters.applyConfiguration(self.misc_configuration)
        for entry in self.misc_configuration:
            gc.writeEntry(entry, QVariant(self.misc_configuration[entry]))

        cc =  FilterConfig()
        self.filter_configuration = self.filterParameters.getConfiguration()
        cc.debug_mode = self.misc_configuration["debugoutput"]
        cc.fromDict(self.filter_configuration)
        self.nagios_server.conf = cc
        
       
        # Open kwallet (synchronous style)
        # NOTE: As above, the wid sent to the wallet is not correct.
        wallet = KWallet.Wallet.openWallet(KWallet.Wallet.LocalWallet(), 0)
        if wallet <> None:
            if not wallet.hasFolder("nagios-plasmoid"):
                wallet.createFolder("nagios-plasmoid")
            wallet.setFolder("nagios-plasmoid")
            wallet.writePassword(self.configParameters.getUsername() + "@" + self.configParameters.getServerName(), self.configParameters.getPassword())
        else:
            # KWallet disabled, we don't do anything...
            pass 
        # Clean up
        wallet = None

        for entry in self.filter_configuration:
            gc.writeEntry(entry, QVariant(self.filter_configuration[entry]))

        gc.writeEntry("servername", QVariant(self.configParameters.getServerName()))        
        gc.writeEntry("nagios_url", QVariant(self.configParameters.getNagiosUrl()))
        gc.writeEntry("nagios_cgi_url", QVariant(self.configParameters.getNagiosCgiUrl()))
        gc.writeEntry("username", QVariant(self.configParameters.getUsername()))
        gc.writeEntry("pollinterval", QVariant(self.configParameters.getPollinterval()))
        gc.sync()
        self.nagios_server.nagios_url = str(self.configParameters.getNagiosUrl())
        self.nagios_server.nagios_cgi_url = str(self.configParameters.getNagiosCgiUrl())
        self.nagios_server.username = str(self.configParameters.getUsername())
        self.nagios_server.password = str(self.configParameters.getPassword())
        self.nagios_server.name = str(self.configParameters.getServerName())
        self.poll_interval = int(self.configParameters.getPollinterval())
        self.update()
        self.checkNagios()
        self.startTimer()

    #
    # configuration canceled
    #
    def configDenied(self):
        self.configParameters.deleteLater()
        self.dialog.reject()
        self.startTimer()


    def fireNotification(self, strType, msg=""):
        if self.misc_configuration["notificationtype"] == "None":
            return True
        types = {}
        types["Persistent"] =  KNotification.Persistent 
        types["CloseWhenWidgetActivated"] =  KNotification.CloseWhenWidgetActivated
        types["CloseOnTimeout"] = KNotification.CloseOnTimeout

        KNotification.event(strType,
                msg,
                QPixmap(),
                None,
                types[self.misc_configuration["notificationtype"]] ,
                KComponentData("nagios-plasmoid", "nagios-plasmoid", KComponentData.SkipMainComponentRegistration)
                )

######################## painting
    def paintInterface(self, painter, option, rect):
        if self.misc_configuration["drawbackground"] == "True":
            painter.fillRect(rect, QBrush(QColor(self.misc_configuration["backgroundcolor"])))
        self.updateIcon(rect)

    def updateIcon(self, rect):
        self.settings = {}
        self.settings["ok"] = self.package().path() + "contents/icons/green.svg"
        self.settings["warning"] = self.package().path() + "contents/icons/yellow.svg"
        self.settings["critical"] = self.package().path() + "contents/icons/red.svg"
        self.settings["textcolor"] = "black"
        self.settings["textsize"] = 60
        self.settings["textfont"] = ""
        # Note: I'm not sure why, but the font shows up much nicer at small sizes when the size is set to larger than the
        #       plasmoid (using a multiple of 2 here)
        loader = KIconLoader()
        size = min(rect.width(),rect.height())*2
        orig_size=size

        ok_icon = KIconLoader.loadIcon(loader, self.settings["ok"], KIconLoader.NoGroup, size)
        warning_icon = KIconLoader.loadIcon(loader, self.settings["warning"], KIconLoader.NoGroup, size)
        critical_icon = KIconLoader.loadIcon(loader, self.settings["critical"], KIconLoader.NoGroup, size)
        pix = ok_icon

        message = 0
        warning = len(self.getWarningServices())
        critical = len(self.getCriticalServices())
        if warning > 0:
            pix = warning_icon
            message = warning
            #message = "Warning %s" %len(self.getWarningServices())
        if critical > 0:
            #message = "Critical: %s %s" %(message , len(self.getCriticalServices()))
            pix = critical_icon
            message = critical

        paint = QPainter(pix)
        paint.setRenderHint(QPainter.SmoothPixmapTransform)
        paint.setRenderHint(QPainter.Antialiasing)

        size = (pix.width() * self.settings["textsize"]) / 100
        if self.misc_configuration["showlabel"] == "True":
            self.drawServerLabel(paint, pix, self.nagios_server.name, size/3)

        self.drawTextOnIcon(paint,pix,message, pix.rect(), size)

        if critical > 0 and warning > 0:
            over = KIconLoader.loadIcon(loader, self.settings["warning"], KIconLoader.NoGroup, orig_size/2, KIconLoader.DefaultState, "", "", True)
            paint.drawPixmap(orig_size*0.5,orig_size*0.5,over)
            target =  QRect(over.rect())
            target.moveTo((pix.width() * 0.5 ),(pix.width() * 0.5 ))
            self.drawTextOnIcon(paint,over,str(warning), target  , size/2)

        paint.end()
        # Update the icon
        self.icon.setIcon(QIcon(pix))
        self.icon.update()

    def drawServerLabel(self,paint, pix, message, size):
       # Set the font
        font = QFont(self.settings["textfont"])
        font.setPixelSize(size)
        font.setBold(True)
        # Check if the font is too big
        fm = QFontMetrics(font)
        if fm.width(str(message)) > pix.width():
            while fm.width(str(message)) > pix.width() and size > 0:
                size = size - 1
                font.setPointSize(size)
                fm = QFontMetrics(font)
        paint.setFont(font)

        # Write the message count
        paint.setPen(QColor(self.settings["textcolor"]))
        text_target =  QRect(pix.rect())
        text_target.moveTo(0 , text_target.height() * 0.05  )
        paint.drawText(text_target, Qt.AlignTop | Qt.AlignHCenter, str(message))

    def drawTextOnIcon(self,paint, pix, message, pos, size):
       # Set the font
        font = QFont(self.settings["textfont"])
        font.setPixelSize(size)
        font.setBold(True)
        # Check if the font is too big
        fm = QFontMetrics(font)
        if fm.width(str(message)) > pix.width():
            while fm.width(str(message)) > pix.width() and size > 0:
                size = size - 1
                font.setPointSize(size)
                fm = QFontMetrics(font)
        paint.setFont(font)

        # Write the message count
        paint.setPen(QColor(self.settings["textcolor"]))
        paint.drawText(pos, Qt.AlignVCenter | Qt.AlignHCenter, str(message))

######################### Notifications

    def initNotifications(self):
        # File versions
        vers = {}
        vers[self.NOTIFYRC] = "11"
        vers["nagios-plasmoid-128.png"] = "1"

        # Setup translations
        kdehome = unicode(KGlobal.dirs().localkdedir())

        # Create notifyrc file if required
        if (not os.path.exists(kdehome+"share/apps/nagios-plasmoid/"+self.NOTIFYRC)) or (self.configured_version <> self.PLASMOID_VERSION):
            if os.path.exists(kdehome+"share/apps"):
                self.createNotifyrc(kdehome, vers)
                gc = self.config()
                gc.writeEntry("notifyrcversion", self.PLASMOID_VERSION)

        # Install icon into system
        ver = "nagios-plasmoid-128.png"
        if ver <> vers["nagios-plasmoid-128.png"]:
            out = commands.getstatusoutput("xdg-icon-resource install --size 128 " + unicode(self.package().path()) + "contents/icons/nagios-plasmoid-128.png nagios-plasmoid")
            if out[0] == 0:
                print "nagios icon installed"
                #gc.writeEntry("gmail-plasmoid-128.png", vers["gmail-plasmoid-128.png"])
            else:
                print "Error installing nagios icon:", out


    def createNotifyrc(self, kdehome, vers):
        # Output the notifyrc file to the correct location
        print "Outputting notifyrc file"

        # Create gmail-plasmoid directory if required
        self.createDirectory(kdehome+"share/apps/nagios-plasmoid")

        # File to create
        fn = kdehome+"share/apps/nagios-plasmoid/"+self.NOTIFYRC

        # File contents
        c = []
        c.append("[Global]\n")
        c.append("Name=Nagios Plasmoid\n")
        c.append("Name[de]=Nagios Plasmoid\n")
        c.append("IconName=nagios-plasmoid\n")
        c.append("Comment=Display Nagios status\n")
    
        c.append("\n")
        c.append("[Event/new-alert]\n")
        c.append("Name=Nagios Plasmoid\n")
        c.append("Contexts=group\n")
        c.append("Name[de]=Nagios Plasmoid\n")
        c.append("Action=Popup\n")
        c.append("\n")

        # Write file
        try:
            f = open(fn,"w")
            f.writelines(c)
            f.close()
            # Update saved version
            gc = self.config()
            gc.writeEntry(self.NOTIFYRC, vers[self.NOTIFYRC])
        except:
            print "Problem writing to file: "+fn
            print "Unexpected error:", sys.exc_info()[0]

    def createDirectory(self, d):
        if not os.path.isdir(d):
            try:
                os.mkdir(d)
            except:
                print "Problem creating directory: "+d
                print "Unexpected error:", sys.exc_info()[0]

    def fixType(self, val):
        # FIXME: This is needed to take care of problems with KDE 4.3 bindings, but it shoudl be removed
        # when things are fixed.
        if type(val) == QVariant:
            return str(val.toString())
        else:
            return val


###################### defaults
    def getDefaultFilterConfiguration(self):
        aconf = {}
        aconf["filter_all_down_hosts"]= "False"
        aconf["filter_all_unreachable_hosts"]= "False"
        aconf["filter_all_unknown_services"]= "False"
        aconf["filter_all_warning_services"]= "False"
        aconf["filter_all_critical_services"]= "False"
        aconf["filter_acknowledged_hosts_services"]= "True"
        aconf["filter_hosts_services_disabled_notifications"]= "False"
        aconf["filter_hosts_services_disabled_checks"]= "True"
        aconf["filter_hosts_services_maintenance"]= "True"
        aconf["filter_services_on_down_hosts"]= "False"
        aconf["filter_services_on_unreachable_hosts"]= "False"
        aconf["filter_services_in_soft_state"]= "True"
        return aconf

    def getDefaultMiscSettings(self):
        settings={}
        settings["notificationtype"] = "Persistent"
        settings["drawbackground"] = "False"
        settings["backgroundcolor"] = '#FFFFFF'
        settings["showlabel"] =  "True"
        settings["debugoutput"] = "False"
        settings["showservicessearchfield"] = "False"
        return settings
    
    def isServiceSearchVisible(self):
        return self.misc_configuration["showservicessearchfield"] == "True"


    def debug(self,message):
        if self.misc_configuration["debugoutput"] == "False":
            return False
        
        formatted = str(time.ctime()) + " - "+  self.nagios_server.name + " - nagios-plasmoid - " + ": " + message
        try:
            filename = os.path.expanduser("~/nagios-plasmoid.log")
            file = open(filename, 'a')
            file.write(formatted)
            file.close()
        except: 
            print "Error writing to file ("+filename+"): " + formatted

def CreateApplet(parent):
    return Nagiosoid(parent)
